#include <cnix/string.h>
#include <cnix/errno.h>
#include <cnix/fs.h>
#include <cnix/kernel.h>

int do_truncate(struct inode *inoptr, off_t length)
{
	return inoptr->i_ops->truncate(inoptr, length);
}

int do_link(const char * srcname, const char * destname)
{
	int error = 0;
	char part[NAME_MAX + 1];
	struct inode * inoptr, * inoptr1, * fino;

	checkname(srcname);
	checkname(destname);

	inoptr = namei0(srcname, &error, 0);
	if(!inoptr)
		return error;

	if(S_ISDIR(inoptr->i_ops->mode(inoptr))){
		iput(inoptr);
		return -EPERM;
	}

	inoptr->i_count++;
	iput(inoptr);

	inoptr1 = namei(destname, &error, 0);
	if(inoptr1){
		iput(inoptr1);
		iput(inoptr);
		return -EEXIST;
	}

	fino = namei(destname, &error, 1);
	if(!fino){
		iput(inoptr);
		return error;
	}

	if(!S_ISDIR(fino->i_ops->mode(fino))){
		iput(fino);
		iput(inoptr);
		return -ENOTDIR;
	}

	if(inoptr->i_dev != fino->i_dev){
		iput(fino);
		iput(inoptr);
		return -EPERM;
	}

	if(!get_tail(destname, part)){
		iput(fino);
		iput(inoptr);
		return -ENAMETOOLONG;
	}

	if(!admit(fino, I_WB)){
		iput(fino);
		iput(inoptr);
		return -EACCES;
	}

	if(!fino->i_ops->additem(fino, part, &error, inoptr->i_num)){
		iput(fino);
		iput(inoptr);
		return error;
	}

	inoptr->i_ops->change_nlinks(inoptr, +1);
	inoptr->i_dirty = 1;
	iput(inoptr);

	iput(fino);

	return 0;
}

int do_unlink(const char * pathname)
{
	int error = 0;
	char part[NAME_MAX + 1];
	struct inode * inoptr, * fino;
	nlink_t nlinks;

	checkname(pathname);

	fino = namei(pathname, &error, 1);
	if(!fino)
		return error;

	if(!S_ISDIR(fino->i_ops->mode(fino))){
		iput(fino);
		return -ENOTDIR;
	}

	if(!get_tail(pathname, part)){
		iput(fino);
		return -ENAMETOOLONG;
	}

	fino->i_count++;
	inoptr = ifind(fino, part);
	if(!inoptr){
		iput(fino);
		return -ENOENT;
	}

	if(admit(inoptr, I_WB) == 0){
		iput(inoptr);
		iput(fino);
		return -EPERM;
	}

	if(S_ISDIR(inoptr->i_ops->mode(inoptr))){
		iput(inoptr);
		iput(fino);
		return -EISDIR;
	}

	nlinks = inoptr->i_ops->change_nlinks(inoptr, -1);
	if(nlinks < 0)
		DIE("BUG: links less than zero\n");

	inoptr->i_dirty = 1;
	iput(inoptr);	// won't block

	ilock(fino);
	if(!fino->i_ops->delitem(fino, part, &error)){
		iput(fino);
		return error;
	}

	iput(fino);
	
	return 0;
}

int do_rename(const char * oldpath, const char * newpath)
{
	int error = 0;
	char part[NAME_MAX + 1], part1[NAME_MAX + 1];
	struct inode * inoptr, * fino, * inoptr1, * fino1;

	checkname(oldpath);
	checkname(newpath);

	if(!get_tail(oldpath, part))
		return -ENAMETOOLONG;

	if(!strcmp(part, ".") || !strcmp(part, ".."))
		return -EINVAL;

	fino = namei(oldpath, &error, 1);
	if(!fino)
		return error;

	if(!S_ISDIR(fino->i_ops->mode(fino))){
		iput(fino);
		return -ENOTDIR;
	}

	fino->i_count++;
	inoptr = ifind(fino, part);
	if(!inoptr){
		iput(fino);
		return error;
	}

	// unlock inoptr to avoid dead lock
	inoptr->i_count++;
	iput(inoptr);

	fino1 = namei(newpath, &error, 1);
	if(!fino1){
		iput(fino);
		iput(inoptr);
		return error;
	}
	
	if(!S_ISDIR(fino1->i_ops->mode(fino1))){
		iput(fino1);
		iput(fino);
		iput(inoptr);
		return -ENOTDIR;
	}

	if(!get_tail(newpath, part1))
		return -ENAMETOOLONG;

	fino1->i_count++;
	inoptr1 = ifind(fino1, part1);
	if(inoptr1){
		iput(inoptr1);
		iput(fino1);
		iput(fino);
		iput(inoptr);
		return -EEXIST;
	}

	// if newpath is the sub directory of oldpath or not
	if(S_ISDIR(inoptr->i_ops->mode(inoptr))
		&& bypass(newpath, &error, 1, 1, inoptr)){
		iput(fino1);
		iput(fino);
		iput(inoptr);
		return -EPERM;
	}

	if(inoptr->i_dev != fino1->i_dev){
		iput(fino1);
		iput(fino);
		iput(inoptr);
		return -EPERM;
	}

	if(!admit(fino, I_WB) || !admit(fino1, I_WB)){
		iput(fino1);
		iput(fino);
		iput(inoptr);
		return -EACCES;
	}

	ilock(fino1);

	if(!fino1->i_ops->additem(fino1, part1, &error, inoptr->i_num)){
		iput(fino1);
		iput(fino);
		iput(inoptr);
		return error;
	}

	if(fino1 != fino){
		fino1->i_count++;
		iput(fino1);
	
		ilock(fino);
	}

	if(!fino->i_ops->delitem(fino, part, &error)){
		int error1;

		if(fino1 != fino)
			ilock(fino1);

		if(!fino1->i_ops->delitem(fino1, part1, &error1))
			printk("so unlucky!\n");

		iput(fino1);
		iput(fino);
		iput(inoptr);

		return error;
	}

	if(S_ISDIR(inoptr->i_ops->mode(inoptr))){
		fino1->i_ops->change_nlinks(fino1, 1);
		fino1->i_update |= MTIME;
		fino1->i_dirty = 1;

		fino->i_ops->change_nlinks(fino, -1);
		fino->i_update |= MTIME;
		fino->i_dirty = 1;
	}

	iput(fino1);
	iput(fino);

	iput(inoptr);

	return 0;
}

int do_symlink(const char * oldpath, const char * newpath)
{
	int error = 0, len, ret;
	char part[NAME_MAX + 1];
	struct inode * inoptr, * fino;

	checkname(oldpath);
	checkname(newpath);

	len = strlen(oldpath);
	if(len >= 4096)
		return -ENAMETOOLONG;

	inoptr = namei(newpath, &error, 0);
	if(inoptr){
		iput(inoptr);
		return -EEXIST;
	}

	fino = namei(newpath, &error, 1);
	if(!fino)
		return error;

	if(!S_ISDIR(fino->i_ops->mode(fino))){
		iput(fino);
		return -ENOTDIR;
	}


	if(!get_tail(newpath, part)){
		iput(fino);
		return -ENAMETOOLONG;
	}

	if(!admit(fino, I_WB)){
		iput(fino);
		return -EACCES;
	}

	inoptr = iinsert(
		fino, part, (0755 & (~current->umask)) | I_LNK, &error
		);
	if(!inoptr){
		iput(fino);
		return error;
	}

	ret = 0;

	// just for simple, inoptr is locked and will be unlocked in do_close
	{
		int fd;

		ret = do_inode_open(inoptr, O_WRONLY);
		if(ret < 0)
			goto errout;

		fd = ret;

		ret = do_write(fd, oldpath, len);
		if(ret != len){
			do_close(fd);
			goto errout;
		}

		do_close(fd);

		ret = 0;
	}

errout:
	iput(inoptr);
	iput(fino);

	return ret;
}

int do_readlink(const char * path, char * buff, size_t buf_size)
{
	int error = 0;
	off_t off;
	unsigned long block;
	struct inode * inoptr;
	struct buf_head * bh;
	int blocksize;

	checkname(path);

	inoptr = namei0(path, &error, 0);
	if(!inoptr)
		return error;

	if(!S_ISLNK(inoptr->i_ops->mode(inoptr))){
		iput(inoptr);
		return -EPERM;
	}

	if(!admit(inoptr, I_RB)){
		iput(inoptr);
		return -EACCES;
	}

	blocksize = inoptr->i_ops->blocksize(inoptr);

	inoptr->i_count++;
	iput(inoptr); // xxx: unlock first

	off = 0;
	while((off < buf_size) && (off < inoptr->i_ops->size(inoptr))){
		int len = blocksize;

		if(len > inoptr->i_ops->size(inoptr) - off)
			len = inoptr->i_ops->size(inoptr) - off;

		if(len > buf_size - off)
			len = buf_size - off;

		block = inoptr->i_ops->bmap(inoptr, off);
		if(block == NOBLK){
			if(memset_user(&buff[off], 0, blocksize) < 0){
				iput(inoptr);
				return -EFAULT;
			}
		}else{
			bh = bread(inoptr->i_dev, block);
			if(!bh){
				iput(inoptr);
				return -EIO;
			}

			if(memcpy_to_user(&buff[off], bh->b_data, len) < 0){
				brelse(bh);
				iput(inoptr);
				return -EFAULT;
			}

			brelse(bh);
		}

		off += len;
	}

	iput(inoptr);

	return off;
}
